////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//                                                                                                                            //
//                                                         WNProject                                                          //
//                                                                                                                            //
//         This file is distributed under the BSD 2-Clause open source license. See Licenses/License.txt for details.         //
//                                                                                                                            //
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

#include "WNScripting/inc/WNFunction.h"
#include "WNScripting/inc/WNTypeElement.h"
#include "WNScripting/inc/WNDeclaration.h"
#include "WNScripting/inc/WNCodeModule.h"
#include "WNScripting/inc/WNDeclList.h"
#include "WNScripting/inc/WNTypeManager.h"
#include "WNScripting/inc/WNScopedVariableList.h"
#include "WNScripting/inc/WNInstructionList.h"
#include "WNScripting/inc/WNNodeHelpers.h"

#ifdef _WN_MSVC
    #pragma warning(push)
    #pragma warning(disable: 4100)
    #pragma warning(disable: 4127)
    #pragma warning(disable: 4152)
    #pragma warning(disable: 4244)
    #pragma warning(disable: 4245)
    #pragma warning(disable: 4267)
    #pragma warning(disable: 4355)
    #pragma warning(disable: 4512)
#endif

#include "llvm/IR/IRBuilder.h"
#include "llvm/IR/Function.h"

#ifdef _WN_MSVC
    #pragma warning(pop)
#endif

using namespace WNScripting; 

WNFunction::WNFunction(WNDeclaration* _decl, WNDeclList* _params, WNInstructionList* _body) :
    mDeclaration(_decl),
    mParameters(_params),
    mBody(_body){
}

WNFunction::~WNFunction() {
    WN_DELETE(mDeclaration);
    if(mParameters) {
        WN_DELETE(mParameters);
    }
    WN_DELETE(mBody);
}

eWNTypeError WNFunction::GenerateCode(WNCodeModule& _module, WNLogging::WNLog& _compilationLog) {
    llvm::IRBuilder<>* builder = reinterpret_cast<llvm::IRBuilder<>*>(_module.GetBuilder());
    eWNTypeError err = eWNOK;
    WNScriptType returnType;
    if(eWNOK != (err = mDeclaration->GetType(_module, returnType))) {
        _compilationLog.Log(WNLogging::eError, 0, "Error cannot resolve type");
        LogLine(_compilationLog, WNLogging::eError);
        return(err);
    }
    std::vector<WNScriptType> parameterTypes;
    if(mParameters) {
        if(eWNOK != (err = mParameters->GetTypeList(_module, parameterTypes, _compilationLog))) {
            _compilationLog.Log(WNLogging::eError, 0, "Error invalid parameters");
            LogLine(_compilationLog, WNLogging::eError);
            return(err);
        }
    }
    WNFunctionDefinition * def = _module.GetFunctionDefinition(mDeclaration->GetName(), parameterTypes);
    if(!def) {
        _compilationLog.Log(WNLogging::eError, 0, "Function Definition does not exist: ", mDeclaration->GetName());
        LogLine(_compilationLog, WNLogging::eError);
        return(eWNError);
    }
    llvm::BasicBlock* bb = llvm::BasicBlock::Create(llvm::getGlobalContext(), "", def->mFunction);
    builder->SetInsertPoint(bb);
    if(mParameters) {
        const WNScriptLinkedList<WNDeclaration>* declarations = NULL;
        if(eWNOK != (err = mParameters->GetDeclarations(declarations))) {
            _compilationLog.Log(WNLogging::eError, 0, "Cannot find declarations for parameters ");
            LogLine(_compilationLog, WNLogging::eError);
            return(err);
        }
        llvm::Function::arg_iterator fIter = def->mFunction->arg_begin();
        WNScriptLinkedList<WNDeclaration>::WNScriptLinkedListNode* iter = declarations->first;
        for(;iter != WN_NULL && fIter != def->mFunction->arg_end(); 
                                            iter = iter->next, ++fIter) {
            fIter->setName(iter->value->GetName());
            WNScriptType t = WN_NULL;
            if(eWNOK != (err = iter->value->GetType(_module, t))) {
                _compilationLog.Log(WNLogging::eError, 0, "Cannot find type for declaration: ", iter->value->GetName());
                LogLine(_compilationLog, WNLogging::eError);
                return(err);
            }
            const GenerateAllocation* alloc = _module.GetTypeManager().GetAllocationOperation(t);
            if(!alloc) {
                _compilationLog.Log(WNLogging::eError, 0, "Cannot allocate variable of type: ", t->mName);
                LogLine(_compilationLog, WNLogging::eError);
                return(eWNCannotCreateType);
            }
            llvm::Value* allocLoc = WN_NULL;
            if(eWNOK != (err = alloc->Execute(_module, t, iter->value->GetName(), false, allocLoc))) {
                _compilationLog.Log(WNLogging::eCritical, 0, "Error allocating a variable of type: ", t->mName);
                LogLine(_compilationLog, WNLogging::eCritical);
                return(err);
            }
            const GenerateAssignment* assign = _module.GetTypeManager().GetAssignmentOperation(t, AT_EQ);
            if(!assign)
            {
                _compilationLog.Log(WNLogging::eError, 0, "Cannot assign a variable of type: ", t->mName);
                LogLine(_compilationLog, WNLogging::eError);
            }
            if(eWNOK != (err = assign->Execute(_module, true, t, fIter, allocLoc, def, _compilationLog))) {
                _compilationLog.Log(WNLogging::eCritical, 0, "Error assigning a variable of type: ", t->mName);
                LogLine(_compilationLog, WNLogging::eCritical);
                return(err);
            }
            WNScriptVariable* var = WN_SCRIPTNODE_NEW(WNScriptVariable(t, iter->value->GetName(), allocLoc));
            if(eWNOK != (err = _module.GetScopedVariableList().PushVariable(var))) {
                _compilationLog.Log(WNLogging::eError, 0, "Variable already exists", iter->value->GetName());
                LogLine(_compilationLog, WNLogging::eError);
                return(err);
            }
        }
        if(fIter != def->mFunction->arg_end() && iter != WN_NULL) {
            _compilationLog.Log(WNLogging::eError, 0, "Invalid number of parameters to function");
            LogLine(_compilationLog, WNLogging::eError);
            return(eWNError);
        }
    }
    
    if(eWNOK != (err = mBody->GenerateCode(_module, def, _compilationLog))) {
        return(err);
    }
    if(!mBody->Returns()) {
        if(eWNOK != (err = _module.GetScopedVariableList().GenerateReturn(_module, def, _compilationLog))) {
            _compilationLog.Log(WNLogging::eCritical, 0, "Cannot create proper return");
            LogLine(_compilationLog, WNLogging::eCritical);
            return(err);
        }
        if(eWNOK != (err = GenerateVOIDReturn(_module, def))) {
            _compilationLog.Log(WNLogging::eError, 0, "Trying to create void return for non-void function");
            LogLine(_compilationLog, WNLogging::eError);
            return(err);
        }
    }
    _module.GetScopedVariableList().ClearScope();
    builder->ClearInsertionPoint();
    
    return(eWNOK);
}
    
eWNTypeError WNFunction::ExportFunctions(WNCodeModule& _module, std::vector<WNFunctionDefinition*>& _definitions, WNLogging::WNLog& _compilationLog) {
    eWNTypeError err = eWNOK;
    WNScriptType returnType;
    if(eWNOK != (err = mDeclaration->GetType(_module, returnType))) {
        return(err);
    }
    std::vector<WNScriptType> parameterTypes;
    if(mParameters) {
        if(eWNOK != (err = mParameters->GetTypeList(_module, parameterTypes, _compilationLog))) {
            LogLine(_compilationLog, WNLogging::eError);
            return(err);
        }
    }
    WNFunctionDefinition* def = WN_NULL;
    if(eWNOK != (err = _module.GenerateFunctionDefinition(mDeclaration->GetName(), parameterTypes, returnType, def))) {
        parameterTypes.clear();
        return(err);
    }

    std::vector<llvm::Type*> llvmTypes;
    for(std::vector<WNScriptType>::iterator i = parameterTypes.begin(); i != parameterTypes.end(); ++i) {
        llvmTypes.push_back((*i)->mLLVMType);
    }

    llvm::FunctionType* fType = llvm::FunctionType::get(returnType->mLLVMType, llvmTypes, WN_FALSE);
    def->mFunctionType = fType;
    //llvm::Function* func = llvm::Function::Create(fType, llvm::GlobalValue::ExternalLinkage, mDeclaration->GetName(), _module.GetModule());
    //def->mFunction = func;
    _definitions.push_back(def);
    return(eWNOK);
}

eWNTypeError WNFunction::GenerateHeader(WNCodeModule& _module, WNLogging::WNLog& _compilationLog) {
    
    eWNTypeError err = eWNOK;
    WNScriptType returnType;
    if(eWNOK != (err = mDeclaration->GetType(_module, returnType))) {
        return(err);
    }
    std::vector<WNScriptType> parameterTypes;
    if(mParameters) {
        if(eWNOK != (err = mParameters->GetTypeList(_module, parameterTypes, _compilationLog))) {
            LogLine(_compilationLog, WNLogging::eError);
            return(err);
        }
    }
    WNFunctionDefinition* def = WN_NULL;
    if(eWNOK != (err = _module.AddFunctionDefinition(mDeclaration->GetName(), parameterTypes, returnType, def))) {
        if(err != eWNAlreadyExists || !def->mCurrentFile) {
            parameterTypes.clear();
            return(err);
        }
    }

    std::vector<llvm::Type*> llvmTypes;
    for(std::vector<WNScriptType>::iterator i = parameterTypes.begin(); i != parameterTypes.end(); ++i) {
        llvmTypes.push_back((*i)->mLLVMType);
    }

    llvm::FunctionType* fType = llvm::FunctionType::get(returnType->mLLVMType, llvmTypes, WN_FALSE);
    llvm::Function* func = llvm::Function::Create(fType, llvm::GlobalValue::InternalLinkage, mDeclaration->GetName(), _module.GetModule());
    def->mFunction = func;
    def->mFunctionType = fType;

    return(eWNOK);
}

